/*
 * Miccsnookersim, a snooker simulation with AI
 * Copyright (C) 2009 Enno Ruijters, Frans van den Heuvel, Yannick Thimister,
 * Ali B. Ozmen, Esther Verhoef, Achim Leydecker
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
import java.util.Random;
import java.awt.Color;
 
public class MonteCarloAI2 implements Player
{
	private static final int DEFAULT_TIMESETTING = 500; //default is 500 iterations
	private static final int DEFAULT_FORCEMODE = 100;
	private int timesetting;
	private int forcemode;
	private Table table;
	private Random generator;
	private boolean viewShots; // to show how he is taking test-shots
	private Display display; // needed to show those test-shots
	private String name;
	private boolean firstShot;
	
	/**
		Creates an instance of the AI
		@param i an int value that decides how many random-samples to take  
		@param j an int value for the amount of force that is added to each shot (which part of the 500 is fixed, tweaking param)
		@param g The Table to play on
		@param d The display (optional but then you shouldn't try to print the testing-shots
		@param name The name of this AI
	*/
	public MonteCarloAI2(int i,int j, Table g, Display d, String name)
	{
		timesetting = i;
		forcemode = j;
		table = g;
		display = d;
		generator = new Random();
		viewShots = false;
		this.name = name;
		firstShot = true;
	} 
	
	/**
		Creates an instance of the AI
		@param i an int value that decides how many random-samples to take 
		@param g The Table to play on
		@param d The display (optional but then you shouldn't try to print the testing-shots
		@param name The name of this AI
	*/
	public MonteCarloAI2(int i, Table g, Display d, String name)
	{
		timesetting = i;
		forcemode = DEFAULT_FORCEMODE;
		table = g;
		display = d;
		generator = new Random();
		viewShots = false;
		this.name = name;
		firstShot = true;
	}
	
	/**
		Creates an instance of the Ai with the default setting for the amount of samples
		@param g The Table to play on
		@param d The display (optional but then you shouldn't try to print the testing-shots
		@param name The name of this AI
	*/
	public MonteCarloAI2(Table g, Display d, String name)
	{
		timesetting = DEFAULT_TIMESETTING;
		forcemode = DEFAULT_FORCEMODE;
		table = g;
		display = d;
		generator = new Random();
		viewShots = false;
		this.name = name;
		firstShot = true;
	}
	
	/**
		Returns the next shot that the AI will be making
		@return Cue object with the parameters set for the next shot
	*/
	public Cue getShot()
	{
		Table AITable; 
		Cue shooter;
		int worst, points, worstShot;
		Simulation sim;
		
		Cue[] bestShots = new Cue[50]; // the best of the best
		int[] pointsBestShots = new int[50]; // and their points
		
		Ball cueBall = null; // the search for the cueBall
		for (Ball b : table.getBalls())
		{
			if (b.getColor() == Color.WHITE)
			{
				cueBall = b;
			}
		}
		
		/* First I fill up the bestShots/pointsBestShots array */
		for(int i = 0; i < bestShots.length; i++) // a copy from the normal random shot function
		{
			AITable = table.clone();
			shooter = new Cue(AITable, null); // new Cue with a table (AND NO FRAME, if needed it needs to be set manually)
			sim = new Simulation(AITable, display);
			
			Vector force;
			/* 	A part of the force is fixed (ie. every shot has at least that amount of force and the rest is randomly generated 
				The First shot shouldn't be top the right, alot of useless shots go that way also the force should be higher*/
			if(!firstShot)
			{
				force = new Vector((generator.nextInt(1000 - forcemode) + forcemode - 500), (generator.nextInt(1000 - forcemode) - 500 + forcemode), 0); 
			}
			else
			{
				force = new Vector((generator.nextInt(500 - forcemode) + forcemode), (generator.nextInt(1000 - forcemode) + forcemode - 500), 0); //firstshot-tries need to have quite some force to be able to pot a red ball
			}
			shooter.setForce(force); //random amount of force is set
			
			try //there should be a Cueball but the Error needs to be catched!
			{
				shooter.setPosition(cueBall.getPosition());//Rotate the Cueshot randomly around the ball
			}
			catch(Exception e)
			{
				System.out.println("MonteCarloError01: Couldn't find a cueball ");
				e.printStackTrace(); //needs some changing in the end product
			}
			
			if(display != null)
			{
				sim.performShot(shooter,viewShots, false);
			}
			else
			{
				sim.performShot(shooter,false, false);
			}
			
			pointsBestShots[i] = evaluate(AITable);
			bestShots[i] = shooter;
		}
		
		for(int i = 0; i < timesetting; i++) // next its time to find the best shots through repeated random sampling
		{
			AITable = table.clone();
			shooter = new Cue(AITable, null); // new Cue with a table (AND NO FRAME, if needed it needs to be set manually)
			sim = new Simulation(AITable, display);
			
			Vector force;
			
			/* 	A part of the force is fixed (ie. every shot has at least that amount of force and the rest is randomly generated 
				The First shot shouldn't be top the right, alot of useless shots go that way also the force should be higher*/
			if(!firstShot)
			{
				force = new Vector((generator.nextInt(1000 - forcemode) + forcemode - 500), (generator.nextInt(1000 - forcemode) - 500 + forcemode), 0); 
			}
			else
			{
				force = new Vector((generator.nextInt(500 - forcemode) + forcemode), (generator.nextInt(1000 - forcemode) + forcemode - 500), 0); //firstshot-tries need to have quite some force to be able to pot a red ball
			}
			shooter.setForce(force); //random amount of force is set
			
			try //there should be a Cueball but the Error needs to be catched!
			{
				//Vector v = new Vector(Ball.RADIUS, 0, 0);
				//shooter.setPosition(Vector.add(cueBall.getPosition(), v.rotate_(generator.nextDouble() * (Math.PI))));//Rotate the Cueshot randomly around the ball 
				shooter.setPosition(cueBall.getPosition());//Rotate the Cueshot randomly around the ball
			}
			catch(Exception e)
			{
				System.out.println("MonteCarloError01: Couldn't find a cueball ");
				e.printStackTrace(); //needs some changing in the end product
			}
			
			if(display != null)
			{
				sim.performShot(shooter,viewShots, false);
			}
			else
			{
				sim.performShot(shooter,false, false);
			}
			
			/* Shot evaluation */
			/* first find the worst shot we have in our little array */
			worstShot = findWorstShot(pointsBestShots);
			worst = pointsBestShots[worstShot];
			/* next check wether this shot is better or not */
			points = evaluate(AITable);
			if(points > worst)
			{
				bestShots[worstShot] = shooter;
				pointsBestShots[worstShot] = points;
			}
		}
		
		/* Now there is an array with the x best shots and it needs to be iterated */
		for(int i = 0; i < bestShots.length; i++)
		{
			for(int j = 0; j < 19; j++) // all shots have a total of 20 extra iterations 
			{
				AITable = table.clone();
				sim = new Simulation(AITable, display);
				
				sim.performShot(bestShots[i],false, true);
				pointsBestShots[i] += evaluate(AITable);
			}
		}
		
		int highest = pointsBestShots[0];
		int place = 0;
		for(int i = 1; i < pointsBestShots.length; i++)
		{
			if(pointsBestShots[i] > highest)
			{
				place = i;
				highest = pointsBestShots[i];
			}
		}
		
		//TEST
		System.out.println("Final eval");
		for(int p = 0; p < bestShots.length; p++)
		{
			System.out.println((p + 1) + " : " + pointsBestShots[p]);
		}
		//TEST
		
		
		firstShot = false; // after one shot the first shot is always done
		
		
		/* need to make a new Cue that plays on the original table */
		Cue out = bestShots[place];
		System.out.println((place + 1) + " : " + pointsBestShots[place]);
		return out;
	}
	
	/**
		Evaluates the last shot done
		@param eval the Table on which the last shot was performed
		@return Integer indicating the value of the last shot (higher = better)
	*/
	private int evaluate(Table eval) 
	{
		GameLogic logic = eval.getGameLogic();
		int out = logic.getPointsLastShot();
		return out;
	}
	
	/**
		First the lowest int in an array of ints
		@param an int array
		@return an int with the place of the lowest one;
	*/
	private int findWorstShot(int[] points)
	{
		int lowest = points[0];
		int place = 0;
		for(int i = 1; i < points.length; i++)
		{
			if(points[i] < lowest)
			{
				place = i;
			}
		}
		return place;
	}
	
	/**
		Sets the option to view how the Ai tries alot of shots or not (recommended: false)
		Do not try this when the Display isn't set!
		@param b Boolean setting the option (true = viewing the shots)
	*/
	public void setViewShots(boolean b)
	{
		viewShots = b;
	}
	
	/**
		Returns what it's current setting is for viewing shots
		@return Boolean with the setting (true = viewing shots)
	*/
	public boolean getViewShots()
	{
		return viewShots;
	}

	/**
		Places the ball back on the table if it has been potted
		@param t The table
		@param cueBall the Cue ball you want to be placed back
	*/
	public synchronized void replaceCueBall(Table t, Ball cueBall)
	{
		cueBall.setPosition(new Vector(285, 100, 0)); //default starting postion
		t.replaceBall(cueBall);
		if (t == table) { /* Not a simulation run */
			Log.logMessage("Ball placed:");
			Log.logVector(cueBall.getPosition());
		}
	}

	/**
		Returns the name of the AI
		@return a String object with the name
	*/
	public String getName()
	{
		return name;
	}

	public synchronized void terminate()
	{
		/* Nothing to do: getShot can be safely interrupted, and
		 * replaceBall is also synchronized, so it will not be
		 * called anymore.
		 */
	}

	public void setTable(Table newTable)
	{
		table = newTable;
	}
}
